/*  This file is part of YATracker.
    YATracker is free software: you can redistribute it and/or modify it under the terms
of the GNU Lesser General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.
    YATracker is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
    You should have received a copy of the GNU Lesser General Public License along with
YATracker. If not, see <http://www.gnu.org/licenses/>. */

// mainwindow.cpp
//
// About OSM tile URL format see: http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDesktopWidget>
#include <QFileDialog>
#include <QDoubleValidator>
#include <QIntValidator>
#include <QNetworkRequest>
#include <math.h>


const quint32 mapFileHeaderSize = 80;
const quint16 mapFileIndexItemSize = 48;
const quint32 mapFileGoogleProj = 900913;  // Google Mercator projection code
const char mapFilePrefix[] = "YAT0";


static void ConvertLatLon2XY(double lat, double lon, int zoom, int& x, int& y)
{
    double xf = (lon + 180.0) / 360.0 * pow(2.0, zoom);
    double yf = (1.0 - log( tan(lat * M_PI/180.0) + 1.0 / cos(lat * M_PI/180.0)) / M_PI) / 2.0 * pow(2.0, zoom);
    x = (int)floor(xf);
    y = (int)floor(yf);
}
static double ProjectF(double lat)
{
    lat = lat * M_PI / 180.0;  // Degree to radians
    return log(tan(lat) + (1.0 / cos(lat)));
}
static double ProjectMercToLat(double v)
{
    return 180.0 / M_PI * atan(sinh(v));
}
static void CalcLatLonFromTileXY(int x, int y, int zoom, double& lon1, double& lat1, double& lon2, double& lat2)
{
    double unit = 1.0 / pow(2.0, zoom);
    double relY1 = y * unit;
    double relY2 = relY1 + unit;
    double limitY = ProjectF(85.0511);
    double rangeY = 2.0 * limitY;
    relY1 = limitY - rangeY * relY1;
    relY2 = limitY - rangeY * relY2;
    lat1 = ProjectMercToLat(relY1);
    lat2 = ProjectMercToLat(relY2);
    unit = 360.0 / pow(2.0, zoom);
    lon1 = -180.0 + x * unit;
    lon2 = lon1 + unit;
}


MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    this->readyToStart = false;
    this->downloadStarted = false;

    ui->setupUi(this);

    connect(&manager, SIGNAL(finished(QNetworkReply*)), SLOT(downloadFinished(QNetworkReply*)));

    QRect frect = frameGeometry();
    frect.moveCenter(QDesktopWidget().availableGeometry().center());
    move(frect.topLeft());

    for (int z = 13; z < 18; z++)
        ui->comboBoxZoom->addItem(QString::number(z));
    ui->comboBoxZoom->setCurrentIndex(2);

    //ui->comboBoxTileServer->addItem("http://dev.openstreetmap.org/~ojw/Tiles/tile.php/");
    ui->comboBoxTileServer->addItem("http://tile.openstreetmap.org/mapnik/");
    ui->comboBoxTileServer->addItem("http://tah.dev.openstreetmap.org/Tiles/tile/");
    ui->comboBoxTileServer->addItem("http://dev.openstreetmap.org/~ojw/Tiles/cycle.php/");
    ui->comboBoxTileServer->addItem("http://dev.openstreetmap.org/~ojw/Tiles/maplint.php/");

    ui->lineEditMinLat->setValidator(new QDoubleValidator(this));
    ui->lineEditMaxLat->setValidator(new QDoubleValidator(this));
    ui->lineEditMinLon->setValidator(new QDoubleValidator(this));
    ui->lineEditMaxLon->setValidator(new QDoubleValidator(this));

    ui->lineEditMinLat->setText("56.82");
    ui->lineEditMaxLat->setText("56.84");
    ui->lineEditMinLon->setText("53.20");
    ui->lineEditMaxLon->setText("53.26");
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::changeEvent(QEvent *e)
{
    QMainWindow::changeEvent(e);
    switch (e->type()) {
    case QEvent::LanguageChange:
        ui->retranslateUi(this);
        break;
    default:
        break;
    }
}

void MainWindow::on_lineEditUrl_textChanged(QString str)
{
//    int latPos = str.indexOf("lat=");
//    if (latPos >= 0)
//    {
//        int latEnd = str.indexOf("&", latPos);
//        if (latEnd >= 0)
//        {
//            QString strLat = str.mid(latPos, latEnd - latPos);
//            //TODO
//        }
//    }
    //TODO
}

void MainWindow::on_pushButtonDownload_clicked()
{
    if (downloadStarted)
        downloadStop();
    else
        downloadStart();
}

void MainWindow::calculateTiles()
{
    ui->labelTiles->clear();

    // Parse and validate parameters
    ui->labelTiles->clear();
    double minLat = atof(ui->lineEditMinLat->text().toAscii());
    double maxLat = atof(ui->lineEditMaxLat->text().toAscii());
    double minLon = atof(ui->lineEditMinLon->text().toAscii());
    double maxLon = atof(ui->lineEditMaxLon->text().toAscii());
    int zoom = atoi(ui->comboBoxZoom->currentText().toAscii());

    char buffer[10];
    int minx, miny, maxx, maxy;
    ConvertLatLon2XY(maxLat, minLon, zoom, minx, miny);
    ConvertLatLon2XY(minLat, maxLon, zoom, maxx, maxy);
    ui->lineEditMinX->setText(itoa(minx, buffer, 10));
    ui->lineEditMinY->setText(itoa(miny, buffer, 10));
    ui->lineEditMaxX->setText(itoa(maxx, buffer, 10));
    ui->lineEditMaxY->setText(itoa(maxy, buffer, 10));

    int xcount = maxx - minx + 1;
    int ycount = maxy - miny + 1;
    int xpix = xcount * 256;
    int ypix = ycount * 256;

    ui->labelTiles->setText(QString("%1 x %2,  %3 tiles,  %4 x %5 pixels").
        arg(xcount).arg(ycount).arg(xcount * ycount).arg(xpix).arg(ypix));

    this->readyToStart = false;
    if (minx != 0 && miny != 0 && maxx >= minx && maxy >= miny)
    {
        this->tileMinX = minx;
        this->tileMaxX = maxx;
        this->tileMinY = miny;
        this->tileMaxY = maxy;
        this->tileZoom = zoom;
        this->tilesCount = xcount * ycount;
        this->readyToStart = true;
    }
}

void MainWindow::downloadStart()
{
    if (!this->readyToStart)
    {
        //TODO: Show alert message
        return;
    }

    this->mapFileName = ui->lineEditMapFile->text();
    if (this->mapFileName.length() == 0)
    {
        //TODO: Show alert message
        ui->lineEditMapFile->setFocus();
        return;
    }

    // Prepare URL list
    QString baseurl = ui->comboBoxTileServer->currentText();
    tileUrls.clear();
    for (quint32 x = this->tileMinX; x <= this->tileMaxX; x++)
    {
        for (quint32 y = this->tileMinY; y <= this->tileMaxY; y++)
        {
            QString url = QString("%1%2/%3/%4.png").arg(baseurl).arg(this->tileZoom).arg(x).arg(y);
            this->tileUrls.append(url);
        }
    }

    if (QFile::exists(this->mapFileName))
    {
        QFile::remove(this->mapFileName);
    }
    this->mapFile.setFileName(this->mapFileName);
    this->mapFile.open(QIODevice::WriteOnly | QIODevice::Append);
    this->tilesDone = 0;

    double lon1, lat1, lon2, lat2;
    double lattmp, lontmp;
    CalcLatLonFromTileXY(this->tileMinX, this->tileMinY, this->tileZoom, lon1, lat2, lontmp, lattmp);
    CalcLatLonFromTileXY(this->tileMaxX, this->tileMaxY, this->tileZoom, lontmp, lattmp, lon2, lat1);

    // Write YAT file header
    quint32 mapFileDataStart = 80 + 48 * this->tilesCount;
    quint16 mapZoom = this->tileZoom;
    char bufferHeader[mapFileHeaderSize];
    memset(bufferHeader, 0, sizeof(bufferHeader));
    memcpy(bufferHeader, mapFilePrefix, 4);
    memcpy(bufferHeader + 4,  &mapFileHeaderSize, 4);
    memcpy(bufferHeader + 8,  &mapFileIndexItemSize, 2);
    memcpy(bufferHeader + 10, &(this->tilesCount), 4);
    memcpy(bufferHeader + 14, &mapFileDataStart, 4);
    float* bounds = (float*)(bufferHeader + 22);
    bounds[0] = (float)lon1;
    bounds[1] = (float)lat1;
    bounds[2] = (float)lon2;
    bounds[3] = (float)lat2;
    memcpy(bufferHeader + 38, &mapFileGoogleProj, 4);
    memcpy(bufferHeader + 42, &mapZoom, 2);
    memcpy(bufferHeader + 44, &this->tileMinX, 4);
    memcpy(bufferHeader + 48, &this->tileMinY, 4);
    memcpy(bufferHeader + 52, &this->tileMaxX, 4);
    memcpy(bufferHeader + 56, &this->tileMaxY, 4);
    bufferHeader[mapFileHeaderSize - 1] = 0xff;  // Mark the end of the header
    this->mapFile.write((const char*)&bufferHeader, mapFileHeaderSize);

    // Write empty index
    char bufferIndex[mapFileIndexItemSize];
    memset(bufferIndex, 0, sizeof(bufferIndex));
    bufferIndex[mapFileIndexItemSize - 1] = 0x7f;  // Mark the end of the index record
    for (int i = 0; i < this->tilesCount; i++)
    {
        this->mapFile.write((const char*)&bufferIndex, mapFileIndexItemSize);
    }

    ui->groupBoxArea->setEnabled(false);
    ui->lineEditMapFile->setEnabled(false);
    ui->pushButtonMapFileBrowse->setEnabled(false);
    ui->pushButtonDownload->setText("Stop");
    ui->progressBar->setEnabled(true);
    ui->progressBar->setValue(0);
    ui->progressBar->setMaximum(this->tilesCount);

    ui->labelMapFileSize->setText(QString("%1 B").arg(this->mapFile.pos()));

    downloadStarted = true;

    // Start download
    startDownload(this->tileUrls.first());
}

void MainWindow::startDownload(QString url)
{
    QNetworkRequest request(url);
    reply = manager.get(request);
}

void MainWindow::downloadFinished(QNetworkReply *reply)
{
    QString url = reply->url().toString();

    // Save response to the map file
    quint64 posBlockStart = this->mapFile.pos();
    this->mapFile.write(reply->readAll());
    quint64 posBlockEnd = this->mapFile.pos();
    quint32 blockLength = (quint32)(posBlockEnd - posBlockStart);
    quint32 mapFileDataStart = 80 + 48 * this->tilesCount;
    quint32 blockStart = (quint32)(posBlockStart - mapFileDataStart);

    // Parse tilex and tiley from the url and calculate tile bounds
    QStringList surl = url.split(QRegExp("/|\\."));
    quint32 tilex = atoi(surl[surl.count() - 3].toAscii());
    quint32 tiley = atoi(surl[surl.count() - 2].toAscii());
    double lon1, lat1, lon2, lat2;
    CalcLatLonFromTileXY(tilex, tiley, this->tileZoom, lon1, lat2, lon2, lat1);

    // Write index record about the block
    const quint16 mapFileTileBlock = 0x0101;
    quint16 mapZoom = this->tileZoom;
    char bufferIndex[mapFileIndexItemSize];
    memset(bufferIndex, 0, sizeof(bufferIndex));
    bufferIndex[mapFileIndexItemSize - 1] = 0x7f;  // Mark the end of the index record
    memcpy(bufferIndex + 0, &blockStart, 4);
    memcpy(bufferIndex + 4, &blockLength, 4);
    memcpy(bufferIndex + 8, &mapFileTileBlock, 2);
    float* bounds = (float*)(bufferIndex + 10);
    bounds[0] = (float)lon1;
    bounds[1] = (float)lat1;
    bounds[2] = (float)lon2;
    bounds[3] = (float)lat2;
    memcpy(bufferIndex + 26, &mapZoom, 2);
    memcpy(bufferIndex + 28, &tilex, 4);
    memcpy(bufferIndex + 32, &tiley, 4);
    this->mapFile.seek(80 + 48 * this->tilesDone);  // Seek to proper index item position
    this->mapFile.write((const char*)&bufferIndex, mapFileIndexItemSize);
    this->mapFile.seek(posBlockEnd);  // Return to the end of the file
    this->tilesDone++;

    this->reply->deleteLater();
    this->reply = NULL;

    // Remove the URL from the list
    this->tileUrls.removeOne(url);

    ui->progressBar->setValue(this->tilesDone);
    ui->labelMapFileSize->setText(QString("%1 B").arg(this->mapFile.pos()));

    // Start next download
    if (this->tileUrls.isEmpty())
    {
        // Write data size into the file header
        quint32 mapDataSize = (quint32)(this->mapFile.pos() - mapFileHeaderSize + mapFileIndexItemSize * this->tilesCount);
        this->mapFile.seek(18);
        this->mapFile.write((const char*)&mapDataSize, 4);

        // Finished successfully
        downloadStop();
    }
    else
        startDownload(this->tileUrls.first());
}

void MainWindow::downloadStop()
{
    if (this->reply != NULL)
    {
        this->reply->abort();
        this->reply->deleteLater();
    }

    this->mapFile.close();

    ui->groupBoxArea->setEnabled(true);
    ui->lineEditMapFile->setEnabled(true);
    ui->pushButtonMapFileBrowse->setEnabled(true);
    ui->pushButtonDownload->setText("Download");
    ui->progressBar->setEnabled(false);
    ui->progressBar->setValue(0);

    downloadStarted = false;
}

void MainWindow::on_pushButtonMapFileBrowse_clicked()
{
    QFileDialog dlg;
    dlg.setAcceptMode(QFileDialog::AcceptSave);
    dlg.setNameFilter("YATraker files (*.yat)");
    dlg.setDefaultSuffix("yat");
    if (dlg.exec() == QDialog::Rejected)
        return;

    ui->lineEditMapFile->setText(dlg.selectedFiles().at(0));
}

void MainWindow::on_lineEditMinLat_textChanged(QString )
{
    calculateTiles();
}
void MainWindow::on_lineEditMinLon_textChanged(QString )
{
    calculateTiles();
}
void MainWindow::on_lineEditMaxLat_textChanged(QString )
{
    calculateTiles();
}
void MainWindow::on_lineEditMaxLon_textChanged(QString )
{
    calculateTiles();
}
void MainWindow::on_comboBoxZoom_currentIndexChanged(QString )
{
    calculateTiles();
}
